﻿using UnityEngine;

namespace AmazingAssets
{
    namespace CurvedWorld
    {
        public enum BEND_TYPE
        {
            ClassicRunner_X_Positive = 0, ClassicRunner_X_Negative, ClassicRunner_Z_Positive, ClassicRunner_Z_Negative,
            LittlePlanet_X, LittlePlanet_Y, LittlePlanet_Z,
            CylindricalTower_X, CylindricalTower_Z,
            CylindricalRolloff_X, CylindricalRolloff_Z,

            SpiralHorizontal_X_Positive, SpiralHorizontal_X_Negative, SpiralHorizontal_Z_Positive, SpiralHorizontal_Z_Negative,
            SpiralHorizontalDouble_X, SpiralHorizontalDouble_Z,
            SpiralHorizontalRolloff_X, SpiralHorizontalRolloff_Z,

            SpiralVertical_X_Positive, SpiralVertical_X_Negative, SpiralVertical_Z_Positive, SpiralVertical_Z_Negative,
            SpiralVerticalDouble_X, SpiralVerticalDouble_Z,
            SpiralVerticalRolloff_X, SpiralVerticalRolloff_Z,

            TwistedSpiral_X_Positive, TwistedSpiral_X_Negative, TwistedSpiral_Z_Positive, TwistedSpiral_Z_Negative,
        };

        [ExecuteAlways]
        public class CurvedWorldController : MonoBehaviour
        {
            public enum AXIS_TYPE { Transform, Custom, CustomNormalized }

            public BEND_TYPE bendType;
            [Range(1, 32)]
            public int bendID = 1;

            public Transform bendPivotPoint; public Vector3 bendPivotPointPosition;
            public Transform bendRotationCenter; public Vector3 bendRotationCenterPosition; public Vector3 bendRotationAxis; public AXIS_TYPE bendRotationAxisType;
            public Transform bendRotationCenter2; public Vector3 bendRotationCenter2Position;


            public float bendVerticalSize, bendVerticalOffset;
            public float bendHorizontalSize, bendHorizontalOffset;
            public float bendCurvatureSize, bendCurvatureOffset;

            public float bendAngle;
            public float bendAngle2;
            public float bendMinimumRadius;
            public float bendMinimumRadius2;
            public float bendRolloff;

            public bool disableInEditor = false;
            public bool manualUpdate = false;


            BEND_TYPE previousBentType;
            int previousID;

            int materialPropertyID_PivotPoint;
            int materialPropertyID_RotationCenter;
            int materialPropertyID_RotationCenter2;
            int materialPropertyID_RotationAxis;
            int materialPropertyID_BendSize;
            int materialPropertyID_BendOffset;
            int materialPropertyID_BendAngle;
            int materialPropertyID_BendMinimumRadius;
            int materialPropertyID_BendRolloff;


#if UNITY_EDITOR
            public bool isExpanded = true;
#endif




            void OnDisable()
            {
                DisableBend();
            }

            void OnDestroy()
            {
                DisableBend();
            }

            void OnEnable()
            {
                EnableBend();
            }

            void Start()
            {
                GenerateShaderPropertyIDs();
            }

            void Update()
            {
                if (manualUpdate)
                    return;

                UpdateShaderdata();
            }

            private void OnDrawGizmos()
            {
                Gizmos.color = Color.yellow;
                switch (bendType)
                {
                    case BEND_TYPE.TwistedSpiral_X_Positive:
                    case BEND_TYPE.TwistedSpiral_X_Negative:
                    case BEND_TYPE.TwistedSpiral_Z_Positive:
                    case BEND_TYPE.TwistedSpiral_Z_Negative:
                        {
                            Gizmos.DrawRay(bendPivotPointPosition, bendRotationAxis.normalized * 10);

                        }
                        break;
                }
            }


            void UpdateShaderdata()
            {
                CheckBendChanging();


                if (isActiveAndEnabled == true)
                {
                    if (disableInEditor && Application.isEditor && Application.isPlaying == false)
                    {
                        UpdateShaderDataDisabled();
                    }
                    else
                    {
                        if (bendPivotPoint != null)
                            bendPivotPointPosition = bendPivotPoint.transform.position;

                        if (bendRotationCenter != null)
                            bendRotationCenterPosition = bendRotationCenter.position;

                        if (bendRotationCenter2 != null)
                            bendRotationCenter2Position = bendRotationCenter2.position;


                        switch (bendType)
                        {
                            case BEND_TYPE.ClassicRunner_X_Positive:
                            case BEND_TYPE.ClassicRunner_X_Negative:
                            case BEND_TYPE.ClassicRunner_Z_Positive:
                            case BEND_TYPE.ClassicRunner_Z_Negative:
                                {
                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalVector(materialPropertyID_BendSize, new Vector2(bendVerticalSize, bendHorizontalSize));
                                    Shader.SetGlobalVector(materialPropertyID_BendOffset, new Vector2(bendVerticalOffset, bendHorizontalOffset));

                                }
                                break;

                            case BEND_TYPE.LittlePlanet_X:
                            case BEND_TYPE.LittlePlanet_Y:
                            case BEND_TYPE.LittlePlanet_Z:
                            case BEND_TYPE.CylindricalTower_X:
                            case BEND_TYPE.CylindricalTower_Z:
                            case BEND_TYPE.CylindricalRolloff_X:
                            case BEND_TYPE.CylindricalRolloff_Z:
                                {
                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalFloat(materialPropertyID_BendSize, bendCurvatureSize);
                                    Shader.SetGlobalFloat(materialPropertyID_BendOffset, bendCurvatureOffset);

                                }
                                break;

                            case BEND_TYPE.SpiralHorizontal_X_Positive:
                            case BEND_TYPE.SpiralHorizontal_X_Negative:
                            case BEND_TYPE.SpiralHorizontal_Z_Positive:
                            case BEND_TYPE.SpiralHorizontal_Z_Negative:
                            case BEND_TYPE.SpiralVertical_X_Positive:
                            case BEND_TYPE.SpiralVertical_X_Negative:
                            case BEND_TYPE.SpiralVertical_Z_Positive:
                            case BEND_TYPE.SpiralVertical_Z_Negative:
                                {
                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalVector(materialPropertyID_RotationCenter, bendRotationCenterPosition);
                                    Shader.SetGlobalFloat(materialPropertyID_BendAngle, bendAngle);
                                    Shader.SetGlobalFloat(materialPropertyID_BendMinimumRadius, bendMinimumRadius);

                                }
                                break;

                            case BEND_TYPE.SpiralHorizontalDouble_X:
                            case BEND_TYPE.SpiralHorizontalDouble_Z:
                            case BEND_TYPE.SpiralVerticalDouble_X:
                            case BEND_TYPE.SpiralVerticalDouble_Z:
                                {
                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalVector(materialPropertyID_RotationCenter, bendRotationCenterPosition);
                                    Shader.SetGlobalVector(materialPropertyID_RotationCenter2, bendRotationCenter2Position);
                                    Shader.SetGlobalVector(materialPropertyID_BendAngle, new Vector2(bendAngle, bendAngle2));
                                    Shader.SetGlobalVector(materialPropertyID_BendMinimumRadius, new Vector2(bendMinimumRadius, bendMinimumRadius2));
                                }
                                break;

                            case BEND_TYPE.SpiralHorizontalRolloff_X:
                            case BEND_TYPE.SpiralHorizontalRolloff_Z:
                            case BEND_TYPE.SpiralVerticalRolloff_X:
                            case BEND_TYPE.SpiralVerticalRolloff_Z:
                                {
                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalVector(materialPropertyID_RotationCenter, bendRotationCenterPosition);
                                    Shader.SetGlobalFloat(materialPropertyID_BendAngle, bendAngle);
                                    Shader.SetGlobalFloat(materialPropertyID_BendMinimumRadius, bendMinimumRadius);
                                    Shader.SetGlobalFloat(materialPropertyID_BendRolloff, bendRolloff);
                                }
                                break;

                            case BEND_TYPE.TwistedSpiral_X_Positive:
                            case BEND_TYPE.TwistedSpiral_X_Negative:
                            case BEND_TYPE.TwistedSpiral_Z_Positive:
                            case BEND_TYPE.TwistedSpiral_Z_Negative:
                                {
                                    switch (bendRotationAxisType)
                                    {
                                        case AXIS_TYPE.Transform:
                                            {
                                                if (bendPivotPoint == null)
                                                {
                                                    switch (bendType)
                                                    {
                                                        case BEND_TYPE.ClassicRunner_X_Positive: bendRotationAxis = Vector3.left; break;
                                                        case BEND_TYPE.ClassicRunner_X_Negative: bendRotationAxis = Vector3.right; break;
                                                        case BEND_TYPE.ClassicRunner_Z_Positive: bendRotationAxis = Vector3.back; break;
                                                        case BEND_TYPE.ClassicRunner_Z_Negative: bendRotationAxis = Vector3.forward; break;
                                                    }
                                                }
                                                else
                                                {
                                                    bendRotationAxis = bendPivotPoint.forward;
                                                }
                                            }
                                            break;

                                        case AXIS_TYPE.Custom:
                                            break;

                                        case AXIS_TYPE.CustomNormalized:
                                            bendRotationAxis = bendRotationAxis.normalized;
                                            break;
                                    }

                                    Shader.SetGlobalVector(materialPropertyID_PivotPoint, bendPivotPointPosition);
                                    Shader.SetGlobalVector(materialPropertyID_RotationAxis, bendRotationAxis);
                                    Shader.SetGlobalVector(materialPropertyID_BendSize, new Vector3(bendCurvatureSize, bendVerticalSize, bendHorizontalSize));
                                    Shader.SetGlobalVector(materialPropertyID_BendOffset, new Vector3(bendCurvatureOffset, bendVerticalOffset, bendHorizontalOffset));
                                }
                                break;
                        }
                    }
                }
            }

            void UpdateShaderDataDisabled()
            {
                Shader.SetGlobalVector(materialPropertyID_PivotPoint, Vector3.zero);
                Shader.SetGlobalVector(materialPropertyID_RotationCenter, Vector3.zero);
                Shader.SetGlobalVector(materialPropertyID_RotationCenter2, Vector3.zero);
                Shader.SetGlobalVector(materialPropertyID_RotationAxis, Vector3.zero);

                Shader.SetGlobalVector(materialPropertyID_BendSize, Vector3.zero);
                Shader.SetGlobalFloat(materialPropertyID_BendSize, 0);

                Shader.SetGlobalVector(materialPropertyID_BendOffset, Vector3.zero);
                Shader.SetGlobalFloat(materialPropertyID_BendOffset, 0);

                Shader.SetGlobalVector(materialPropertyID_BendAngle, Vector2.zero);
                Shader.SetGlobalFloat(materialPropertyID_BendAngle, 0);

                Shader.SetGlobalVector(materialPropertyID_BendMinimumRadius, Vector2.zero);
                Shader.SetGlobalFloat(materialPropertyID_BendMinimumRadius, 0);

                Shader.SetGlobalFloat(materialPropertyID_BendRolloff, 10);
            }


            public void DisableBend()
            {
                GenerateShaderPropertyIDs();

                UpdateShaderDataDisabled();
            }

            public void EnableBend()
            {
                GenerateShaderPropertyIDs();


                previousBentType = bendType;
                previousID = bendID;

                UpdateShaderdata();
            }


            public void ManualUpdate()
            {
                UpdateShaderdata();
            }


            void CheckBendChanging()
            {
                if (previousBentType != bendType || previousID != bendID)
                {
                    DisableBend();


                    previousBentType = bendType;

                    if (bendID < 1) bendID = 1;
                    previousID = bendID;


                    GenerateShaderPropertyIDs();
                }
            }

            void GenerateShaderPropertyIDs()
            {
                materialPropertyID_PivotPoint = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_PivotPoint", bendType, bendID));
                materialPropertyID_RotationCenter = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_RotationCenter", bendType, bendID));
                materialPropertyID_RotationCenter2 = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_RotationCenter2", bendType, bendID));
                materialPropertyID_RotationAxis = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_RotationAxis", bendType, bendID));
                materialPropertyID_BendSize = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_BendSize", bendType, bendID));
                materialPropertyID_BendOffset = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_BendOffset", bendType, bendID));
                materialPropertyID_BendAngle = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_BendAngle", bendType, bendID));
                materialPropertyID_BendMinimumRadius = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_BendMinimumRadius", bendType, bendID));
                materialPropertyID_BendRolloff = Shader.PropertyToID(string.Format("CurvedWorld_{0}_ID{1}_BendRolloff", bendType, bendID));
            }


            public Vector3 TransformPosition(Vector3 vertex)
            {
                if (enabled == false || gameObject.activeSelf == false)
                    return vertex;
                else
                    return CurvedWorldUtilities.TransformPosition(vertex, this);
            }

            public Quaternion TransformRotation(Vector3 vertex, Vector3 forwardVector, Vector3 rightVector)
            {
                Vector3 p0 = TransformPosition(vertex);
                Vector3 p1 = TransformPosition(vertex + forwardVector);
                Vector3 p2 = TransformPosition(vertex + rightVector);


                Vector3 forward = Vector3.Normalize(p1 - p0);
                Vector3 right = Vector3.Normalize(p2 - p0);
                Vector3 up = Vector3.Cross(forward, right);

                if (forward.sqrMagnitude < 0.01f && up.sqrMagnitude < 0.01f)
                    return Quaternion.identity;
                else
                    return Quaternion.LookRotation(forward, up);
            }


            public void SetBendVerticalSize(float value)
            {
                bendVerticalSize = value;
            }

            public void SetBendVerticalOffset(float value)
            {
                bendVerticalOffset = value;
            }

            public void SetBendHorizontalSize(float value)
            {
                bendHorizontalSize = value;
            }

            public void SetBendHorizontalOffset(float value)
            {
                bendHorizontalOffset = value;
            }

            public void SetBendCurvatureSize(float value)
            {
                bendCurvatureSize = value;
            }

            public void SetBendCurvatureOffset(float value)
            {
                bendCurvatureOffset = value;
            }

            public void SetBendAngle(float value)
            {
                bendAngle = value;
            }

            public void SetBendAngle2(float value)
            {
                bendAngle2 = value;
            }

            public void SetBendMinimumRadius(float value)
            {
                bendMinimumRadius = value;
            }

            public void SetBendMinimumRadius2(float value)
            {
                bendMinimumRadius2 = value;
            }

            public void SetBendRolloff(float value)
            {
                bendRolloff = value;
            }
        }
    }
}